package explorandum.f09.g5;

import explorandum.GameConstants;

import java.awt.Point;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Set;

public class MapInHash extends Map {
  private HashMap<Point, Cell> map;
  private int min_x = 0, min_y = 0, max_x = 0, max_y = 0;
  
  public Features features;
  
  public MapInHash() {
    map = new HashMap<Point, Cell>();
    features = new Features(this);
  }

  // Add what the player sees to the map.
  public void add(int explorerId, Point currentLocation, Point[] offsets, Boolean[] hasExplorer,
                  Integer[][] otherExplorers, Integer[] terrain, int time, int range, Boolean StepStatus) 
  {
	  /* 
	   * New Fall 2009 code.
	   * 
	   *  assert currentLocation exists, and add to map so the base is present; this just helps clearify the situation.
	   *  
	   */
	  Cell currentCell = getCell(currentLocation);
	  if(currentCell == null) {
		  currentCell = new Cell();
		  currentCell.location = new Point(currentLocation);
		  currentCell.terrain = GameConstants.LAND;

		  map.put(currentLocation, currentCell);
		  buildNeighborLinks(currentCell);
	  }
	  
	  /* 
	   * New Fall 2009 code.
	   * 
	   *   If the current square has a step, then set the "claimed_distance_other" for this cell to be zero if we've never
	   *   been here.  If we have been here, then it doesn't really matter.
	   */
	  
	  if(StepStatus)
	  {
		  Util.debugMessage("StepStatusTrue", "Current Location " + currentLocation.toString() + " and time " + time + " has Footstep");
		  if(currentCell.FirstOnCell == G5Constants.NOT_FIRST_ON_CELL && !currentCell.haveVisited)
		  {
			  /* someone got here before us, boo! */
			  currentCell.claimed_distance_other = 0;
			  //currentCell.claimed_distance ;
			  currentCell.HasFootsteps = G5Constants.HAS_FOOTSTEPS;
			  currentCell.FirstOnCell = G5Constants.NOT_FIRST_ON_CELL;
		  }
		  else if(currentCell.FirstOnCell == G5Constants.FIRST_ON_CELL)
		  {
			  /* we are the first ones here, but there are footsteps?  Something weird *shrug* */
			  currentCell.claimed_distance_other = Double.MAX_VALUE;
			  currentCell.claimed_distance = 0;
			  currentCell.HasFootsteps = G5Constants.HAS_FOOTSTEPS;
			  currentCell.FirstOnCell = G5Constants.NOT_FIRST_ON_CELL;
		  }
	  }
	  else
	  {
		  /* No steps, we're the first ones here; yes! */
		  Util.debugMessage("StepStatusFalse", "Current Location Does Not Have A Footstep = We got here first");
		  currentCell.claimed_distance = 0;
		  currentCell.claimed_distance_other = Double.MAX_VALUE;
		  currentCell.HasFootsteps = G5Constants.HAS_FOOTSTEPS;
		  currentCell.FirstOnCell = G5Constants.FIRST_ON_CELL;
	  }

	  
    // Add mountains first.
    for (int i = 0; i < offsets.length; i++) {
      Point location = offsets[i];
      if (terrain[i] == GameConstants.MOUNTAIN && !map.containsKey(location)) 
      {
        Cell c = new Cell();
        c.location = new Point(location);
        c.terrain = terrain[i];
        c.claimed_distance = Double.MAX_VALUE;
        c.claimed_distance_other = Double.MAX_VALUE;
        c.claimed_time = time;
        c.HasFootsteps = G5Constants.UNKNOWN;
        c.FirstOnCell = G5Constants.UNKNOWN;
        
        map.put(location, c);
        buildNeighborLinks(c);
        
        /* New Fall 2009 changes; add cell to our knowledge of features */
        if(c.terrain != GameConstants.LAND) 
        {
        	Feature feature = features.checkNewPoint(c.location);
        }
      }
    }

    for (int i = 0; i < offsets.length; i++) 
    {
      Point location = offsets[i];
      if (!map.containsKey(location)) 
      {
        Cell c = new Cell();
        
        c.location = new Point(location);
        c.terrain = terrain[i];
        int dx = location.x - currentLocation.x;
        int dy = location.y - currentLocation.y;
        boolean visible = true;
        if (terrain[i] == GameConstants.LAND && Math.abs(dx) == 1 && Math.abs(dy) == 1) 
        {
          Cell cell_1 = getCell(currentLocation.x + dx, currentLocation.y);
          Cell cell_2 = getCell(currentLocation.x, currentLocation.y + dy);
          if ((cell_1 != null && cell_1.terrain == GameConstants.MOUNTAIN) ||
              (cell_2 != null && cell_2.terrain == GameConstants.MOUNTAIN)) 
          {
            visible = false;
          }
        }
/* Monal's changes
        if (visible)
        {
          c.claimed_distance = currentLocation.distance(location);
          if(c.StepStatus) {
        	  if(!c.FirstOnCell){
        		  c.claimed_distance = Double.MAX_VALUE;
        	  }
        	  else
        		  c.StepStatus = true;
          }
        }
        else
        {
        	c.claimed_distance = range + 1;
        	c.claimed_time = time;
        	c.claimed_distance_other = Double.MAX_VALUE; //Initialize the others claimed distance to infinity
*/

        map.put(location, c);
        buildNeighborLinks(c);
        
        // Set claimed distance & claimed distance other thusly:
        //  * If not a land, add to the main feature database
        //  
        //  * If there is a feature and it's been set to claimed, then just go ahead and assume this is already claimed
        //  * Otherwise, if the new distance is closer than any previous time we've seen this square, set the claimed_distance
        //      to this new distance unless StepStatus is true, in which case set claimed_distance_other instead.
        {
        	Feature feature = null;
        	
        	/* New Fall 2009 changes; add cell to our knowledge of features */
            if(c.terrain != GameConstants.LAND) 
            {
            	feature = features.checkNewPoint(c.location);
            }
        	
            
            /* 
             * New Fall 2009 Code
             * 
             * If the cell is attached to a feature that we've marked as "claimed by another"
             * we should set the claimed_distance_other to 0, since we should avoid it
             */
            if(feature != null && feature.claimed) {
            	c.claimed_distance_other = 0;
            }
            else {
            	double distance = currentLocation.distance(location);

            	/*
            	 * New Fall 2009 Code
            	 * 
            	 *   If there are steps in the current square, that means someone got here before us 
            	 *     (This section of code adds a new cell to our map).
            	 *   
            	 *   Since someone got to the current square before us, anything we can now see should
            	 *     be marked as belonging to another player, if that distance is closer than our 
            	 *     last seen distance.
            	 */
            	if(StepStatus) {
            		if(visible && distance < c.claimed_distance) {
            			c.claimed_distance_other = distance;
            		}
            		else if (range + 1 < c.claimed_distance) {
            			c.claimed_distance_other = range + 1;
            		}
            		/* time may as well have been 0 for all we know */
            		c.claimed_time = 0;
            	}
            	else {
            		if (visible)
            		{
            			c.claimed_distance = currentLocation.distance(location);
            		}
            		else
            		{
            			c.claimed_distance = range + 1;
            		}
            		c.claimed_time = time;
            		c.claimed_distance_other = Double.MAX_VALUE; //Initialize the others claimed distance to infinity
            	}
            }
        }         
        
        
        //To add other players data...
        if(hasExplorer[i])
        {
            seeOtherPlayers(explorerId, c, otherExplorers[i], range);
        }
      }
      else
      {
        Cell c = getCell(location);
 
        double distance = currentLocation.distance(location);
        
        /* New Fall 2009 code
         * 
         * If the distance to a given cell 'c' is closer than the previous
         *   claimed distance that we have in storage, then update the Map.
         */
       	if(StepStatus && distance < c.claimed_distance_other) {
       		c.claimed_distance_other = distance;
       		c.claimed_time = 0;
       	}
       	else if(distance < c.claimed_distance) {
       		c.claimed_distance = distance;
       		c.claimed_time = time;
        }
        
        //To add other players data...
        if(hasExplorer[i])
        {
            seeOtherPlayers(explorerId, c, otherExplorers[i], range);
        }
      }
      updateBound(location);
      
    }
    
    /* New Fall 2009 Code
     * 
     * If there are Steps, but we've never visited this cell, we want to increment
     * "visibleFootsteps" of every feature in sight, as this is a shortcut way of
     * deciding what's already taken
     */
    if(StepStatus && !currentCell.haveVisited) {
    	ArrayList<Feature> processed = new ArrayList<Feature>();
    	
    	/* Increment the "visible footstep" counter more for coastline cells */
    	if (true) {
    		ArrayList<Cell> closest = this.getImmediateNeighbors(currentLocation);
    		for(Cell c : closest)
    		{
    			if(c.terrain != GameConstants.LAND) {
    				Feature feat = this.features.getFeatureByPoint(c.location);
    				if(feat != null && !(processed.contains(feat))) {
    					// As we know that these are the closest neighbors, we up the footstep count 
    					feat.visibleFootsteps+=2;
    					processed.add(feat);
    				}
    			}
    		}
    	}
   
   		if(range > 1) {
	    	for(Point p : offsets) {
	    		Cell c = this.getCell(p);
	    		if(c.terrain != GameConstants.LAND) {
	    			Feature feat = this.features.getFeatureByPoint(p);
	    			if(feat != null && !(processed.contains(feat))) {
	    				feat.visibleFootsteps++;
	    				processed.add(feat);
	    			}
	    		}
	    	}
  		}
    }
    
    currentCell.haveVisited = true;
  }
  
  /*
   * Very simple heuristic which assumes a feature is taken if it has 'x' number of footsteps in range
   *   'x' was empirically tested and range dependent.  We expect to see more footsteps in sight of a
   *   feature if range is higher.
   *   
   * Note: this function is NOT expected to be only called when footsteps increment, because we may
   *   apply other heuristics that may have changed the situation.
   */
  public void guessClaimedFeatures(Point currentLocation, int range)
  {
	  /*
	   * neighbors:  our neighboring cells that we can see
	   * processed:  the list of features that are in view that we've examined
	   * newlyClaiemd:  the list of features that have just passed the threshold this turn
	   */
	  ArrayList<Point> neighbors = this.getNeighbors(currentLocation, range);
	  ArrayList<Feature> processed = new ArrayList<Feature>();
	  ArrayList<Feature> newlyClaimed = new ArrayList<Feature>();
	  
	  // currentCell is unused, but I'm saving it for more advanced logic that we may place here later
	  Cell currentCell = this.getCell(currentLocation);
	  
	  /*
	   * First collect all the neighbors and collect all the features that the cells that we can see from here include
	   *   i.e. all water & mountain in range
	   */
	  for(Point p : neighbors) {
		  Cell c = this.getCell(p);
		  if(c != null && c.terrain != GameConstants.LAND) {
			  Feature feature = features.getFeatureByPoint(p);
			  
			  if(feature != null && !(processed.contains(feature))) {
				  processed.add(feature);
		
				  /*
				   * If we haven't processed this feature and it hasn't been claimed, then we should check whether
				   * it passes a threshold
				   */
				  if(feature.claimed == false) {
					  if (feature.visibleFootsteps >= range + G5Constants.CLAIMED_FEATURE_FOOTSTEP_THRESHOLD) {
						  Util.debugMessage("assume_claiming", "A feature including the point " + p.toString() + " was set claimed");
						  feature.claimed = true;
						  newlyClaimed.add(feature);
					  }
				  }
//				  else if (currentCell.HasFootsteps == G5Constants.HAS_FOOTSTEPS && currentCell.FirstOnCell != G5Constants.NOT_FIRST_ON_CELL) {
//					  
//				  }
				  
			  }
		  }
	  }
	  
	  /*
	   * Now we need to go through the features that are 'newly claimed' and set the coastline that we've seen to
	   *   have a claimed_distance_other of 0, i.e. assume those coastline to be claimed.
	   *   
	   * We should also set the neighbors of coastline to be 1, but for now it encourages the Player to walk closer
	   *   to take a better look if we don't.
	   */
	  HashMap<Point, Boolean> processedCells = new HashMap<Point, Boolean>();
	  for (Feature feature : newlyClaimed) {
		  ArrayList<Cell> coastline = feature.getCoastline(this);
		  if(coastline != null) {
			  for(Cell c : coastline) {
				  if(!(processedCells.containsKey(c.location))) {
					  processedCells.put(c.location, true);
					  if(c.claimed_distance != 0) {
						  c.claimed_distance_other = 0;
						  //Util.debugMessage("assume_claiming", "===  Point " + c.location + "  has been assumed to be claimed");
					  }
				  }
			  }
		  }
	  }
  }
  
  /*
   * New Fall 2009 Code
   * 
   * Find the feature, if it exists, that includes the parameter 'p'.
   */
  public ArrayList<Feature> getNeighborFeaturesByPoint(Point p)
  {
	  ArrayList<Feature> rv = new ArrayList<Feature>();
	  
	  Cell cell = this.getCell(p);
	  if(cell != null) 
	  {
		  ArrayList<Cell> neighbors  = this.getImmediateNeighbors(p);
		  for (Cell neighbor : neighbors)
		  {
			  if(neighbor.terrain != GameConstants.LAND)
			  {
				  /* Neighbor is feature, get feature */
				  Feature feat = features.getFeatureByPoint(neighbor.location);
				  if(!rv.contains(feat))
				  {
					  rv.add(feat);
				  }
			  }
		  }
	  }
	  
	  return rv;
  }
  
  public Point getmin()
  {
	  return new Point(min_x,min_y);
  }

  public Point getmax()
  {
	  return new Point(max_x,max_y);
  }


  //To add other players data...
  private void markNeighbors(Point p, int range)
  {
	  ArrayList<Point> neighbors = getNeighbors(p, range);
	  for(Point point : neighbors)
	  {
		  if(point ==null)
			  continue;
		  
		  Cell cell = map.get(point);
		  if(cell == null)
			  continue;
		  
		  double distance = point.distance(p);
		  if(distance < cell.claimed_distance_other){
			  cell.claimed_distance_other = distance;
		  }
	  }
  }
  
  //To add other players data...
  private void seeOtherPlayers(int myId, Cell c, Integer[] otherExplorers, int range){
	  for(int i=0; i<otherExplorers.length; i++)
	  {
		  if(otherExplorers[i] != null)
		  {
			  int val = otherExplorers[i];
			  if(val != myId)
			  {
				  c.claimed_distance_other = 0;
				  c.other_players.add(val);
				  markNeighbors(c.location, range); //Change 3 to the actual range
			  }
		  }
	  }
  }
  
  
  private void buildNeighborLinks(Cell c) 
  {
    for (int d = 1; d <= 8; d++) {
      Cell neighbor = getCell(
          c.location.x + GameConstants._dx[d],
          c.location.y + GameConstants._dy[d]);
      if (neighbor != null && c.terrain == 0 && neighbor.terrain == 0) {
        // Build a link from c to neighbor
        Link link = new Link();
        link.destination = neighbor;
        link.cost = (d % 2 == 1) ? 2 : 3;
        link.direction = d;
        c.neighbors.add(link);

        // Build a link from neighbor to c
        Link r_link = new Link();
        r_link.destination = c;
        r_link.cost = (d % 2 == 1) ? 2 : 3;
        r_link.direction = (d - 1 + 4) % 8 + 1;  // reverse direction
        neighbor.neighbors.add(r_link);
      }
      if (neighbor != null)
        neighbor.unexplored_directions.remove((d - 1 + 4) % 8 + 1);
      else
        c.unexplored_directions.add(d);
    }
  }

  private void updateBound(Point p) {
    if (p.x < min_x)
      min_x = p.x;
    if (p.x > max_x)
      max_x = p.x;
    if (p.y < min_y)
      min_y = p.y;
    if (p.y > max_y)
      max_y = p.y;
  }

  // Get the current bound of the map.
  public Rectangle getBound() {
    return new Rectangle(min_x, min_y, max_x - min_x, max_y - min_y);
  }

  // Get the cell at (x, y). The coordinates are relative to the start point.
  public Cell getCell(int x, int y) {
    return getCell(new Point(x, y));
  }

  // Get the cell at p. The coordinates are relative to the start point.
  public Cell getCell(Point p) {
    return map.get(p);
  }

  // Print debug message to stdout
  public void printDebugMsg() {
    //System.out.println("=== Map ===");
    //for (int y = max_y; y >= min_y; y--) {
    //  for (int x = max_x; x >= min_x; x--) {
    for (int y = min_y; y <= max_y; y++) {
      for (int x = min_x; x <= max_x; x++) {
        Cell c = getCell(x, y);
        if (c != null) {
          //if (c.terrain == 0)
            //System.out.print("_ ");
          //else if (c.terrain == 1)
            //System.out.print("~ ");
          //else
            //System.out.print("^ ");
        } else {
          //System.out.print("? ");
        }
      }
      //System.out.println();
    }
  }

  // Get the coordinates of all discovered cells
  public Set<Point> getAllDiscoveredCoordinates() {
    return map.keySet();
  }
}
